# -*- coding: utf-8 -*-

"""
PDF矩形拼板工具 - 优化版本
从PDF提取的矩形中计算最佳拼板方案
支持多种装箱算法和DPI转换
宽度限制：1米(1000mm)
间距：10mm
矩形允许旋转
目标：计算最短的拼接长度
"""

import os
import json
import argparse
import math
from typing import List, Dict, Tuple, Optional
from enum import Enum

class PackingAlgorithm(Enum):
    """拼板算法类型"""
    SHELF_PACKING = "shelf_packing"  # 分层装箱
    BOTTOM_LEFT_FILL = "bottom_left_fill"  # 底部左填充
    BEST_FIT_DECREASING = "best_fit_decreasing"  # 最佳适配递减
    GUILLOTINE = "guillotine"  # 断头台裁切算法（递归切割）

class RectangleItem:
    """矩形物品类，用于表示要拼板的矩形"""
    def __init__(self, width, height, index=None, source_page=None):
        self.width = width
        self.height = height
        self.index = index
        self.source_page = source_page
        self.x = 0  # 放置位置的x坐标
        self.y = 0  # 放置位置的y坐标
        self.rotated = False  # 是否旋转
        self.area = width * height
        
    def rotate(self):
        """旋转矩形，交换宽高"""
        self.width, self.height = self.height, self.width
        self.rotated = not self.rotated
        
    def get_dimensions(self):
        """获取矩形的尺寸"""
        return self.width, self.height
        
    def get_bounds(self):
        """获取矩形的边界框"""
        return (self.x, self.y, self.x + self.width, self.y + self.height)
        
    def __repr__(self):
        return f"Rectangle(width={self.width:.2f}, height={self.height:.2f}, index={self.index}, rotated={self.rotated})"

class Shelf:
    """货架类，用于分层装箱算法"""
    def __init__(self, height, width_limit, spacing):
        self.height = height
        self.width_limit = width_limit
        self.spacing = spacing
        self.remaining_width = width_limit
        self.items = []
        self.y_position = 0
        
    def can_place(self, item):
        """检查是否可以放置物品"""
        required_width = item.width
        if self.items:
            required_width += self.spacing
        return required_width <= self.remaining_width
        
    def place_item(self, item):
        """放置物品到货架上"""
        if self.items:
            item.x = self.items[-1].x + self.items[-1].width + self.spacing
        else:
            item.x = 0
            
        item.y = self.y_position
        self.items.append(item)
        
        self.remaining_width -= item.width
        if len(self.items) > 1:
            self.remaining_width -= self.spacing

class RectangleNestingOptimized:
    """优化版矩形拼板类，实现多种矩形装箱算法"""
    
    def __init__(self, width_limit=1000, spacing=10, dpi=300, algorithm=PackingAlgorithm.SHELF_PACKING, objective: str = "min_length", objective_weights: Optional[Dict[str, float]] = None):
        self.width_limit = width_limit  # 宽度限制，默认1000mm
        self.spacing = spacing  # 间距，默认10mm
        self.dpi = dpi  # DPI，默认300
        self.algorithm = algorithm
        self.px_to_mm = 25.4 / dpi  # 像素到毫米的转换因子
        self.mm_to_px = dpi / 25.4  # 毫米到像素的转换因子
        
        self.items = []  # 所有要拼板的矩形
        self.shelves = []  # 货架列表（用于分层装箱）
        self.total_length = 0  # 总长度
        self.packing_area = []  # 打包区域（用于其他算法）
        self.objective = objective  # 目标函数：min_length|min_cuts|weighted
        self.objective_weights = objective_weights or {"length": 1.0, "cuts": 0.0, "rotations": 0.0}
        
    def pixels_to_mm(self, pixels):
        """像素转毫米"""
        return pixels * self.px_to_mm
        
    def mm_to_pixels(self, mm):
        """毫米转像素"""
        return mm * self.mm_to_px
        
    def load_from_json(self, json_path):
        """从JSON文件加载矩形数据"""
        try:
            with open(json_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
                
            # 从所有页面提取矩形
            for page in data.get('pages', []):
                page_number = page.get('page_number', 1)
                for idx, rect in enumerate(page.get('detected_rectangles', [])):
                    # 获取矩形尺寸（像素）
                    width_px = rect.get('width', 0)
                    height_px = rect.get('height', 0)
                    
                    # 转换为毫米单位
                    width_mm = self.pixels_to_mm(width_px)
                    height_mm = self.pixels_to_mm(height_px)
                    
                    # 创建矩形物品
                    item = RectangleItem(width_mm, height_mm, index=idx, source_page=page_number)
                    self.items.append(item)
                    
            print(f"成功加载 {len(self.items)} 个矩形")
            print(f"使用DPI: {self.dpi}, 转换系数: 1像素 = {self.px_to_mm:.4f}mm")
            return True
        except Exception as e:
            print(f"加载JSON文件时出错: {str(e)}")
            return False
    
    def sort_items(self, sort_key='area'):
        """对矩形进行排序"""
        if sort_key == 'area':
            self.items.sort(key=lambda x: x.area, reverse=True)
        elif sort_key == 'height':
            self.items.sort(key=lambda x: x.height, reverse=True)
        elif sort_key == 'width':
            self.items.sort(key=lambda x: x.width, reverse=True)
        elif sort_key == 'perimeter':
            self.items.sort(key=lambda x: 2*(x.width + x.height), reverse=True)
    
    def optimize_rotation(self):
        """优化矩形旋转，使宽度尽可能适合宽度限制"""
        for item in self.items:
            # 如果矩形宽度超过限制，尝试旋转
            if item.width > self.width_limit and item.height <= self.width_limit:
                item.rotate()
            # 如果两种方向都超过限制，选择宽度较小的方向
            elif item.width > self.width_limit and item.height > self.width_limit:
                if item.width > item.height:
                    item.rotate()
    
    def shelf_packing_algorithm(self):
        """分层装箱算法"""
        print("使用分层装箱算法...")
        current_y = 0
        
        for item in self.items:
            placed = False
            
            # 尝试放置到现有货架
            for shelf in self.shelves:
                for _ in range(2):  # 尝试两种方向
                    if shelf.can_place(item):
                        shelf.place_item(item)
                        placed = True
                        break
                    item.rotate()
                
                if placed:
                    break
            
            # 如果没有找到合适的货架，创建新货架
            if not placed:
                if item.width > self.width_limit:
                    item.rotate()
                    if item.width > self.width_limit:
                        print(f"警告：矩形 {item.index} 无法放置，尺寸过大")
                        continue
                
                new_shelf = Shelf(item.height, self.width_limit, self.spacing)
                new_shelf.y_position = current_y
                new_shelf.place_item(item)
                self.shelves.append(new_shelf)
                
                current_y += item.height + self.spacing
        
        if self.shelves:
            self.total_length = current_y - self.spacing
    
    def bottom_left_fill_algorithm(self):
        """底部左填充算法"""
        print("使用底部左填充算法...")
        self.packing_area = []
        current_height = 0
        
        for item in self.items:
            placed = False
            
            # 尝试两种方向
            for _ in range(2):
                best_x = float('inf')
                best_y = float('inf')
                
                # 寻找最佳放置位置
                for x in range(0, int(self.width_limit - item.width) + 1, 10):  # 10mm步长
                    # 计算在这个x位置的最低可能y位置
                    max_y = 0
                    for existing_item in self.packing_area:
                        if (x < existing_item.x + existing_item.width + self.spacing and 
                            x + item.width + self.spacing > existing_item.x):
                            max_y = max(max_y, existing_item.y + existing_item.height + self.spacing)
                    
                    if (x + item.width <= self.width_limit and 
                        (best_x == float('inf') or max_y < best_y or 
                         (max_y == best_y and x < best_x))):
                        best_x = x
                        best_y = max_y
                
                # 如果找到了合适位置
                if best_x != float('inf'):
                    item.x = best_x
                    item.y = best_y
                    self.packing_area.append(item)
                    current_height = max(current_height, item.y + item.height)
                    placed = True
                    break
                else:
                    item.rotate()  # 尝试旋转
            
            if not placed:
                print(f"警告：矩形 {item.index} 无法放置")
        
        self.total_length = current_height
    
    def best_fit_decreasing_algorithm(self):
        """最佳适配递减算法"""
        print("使用最佳适配递减算法...")
        # 按高度递减排序
        self.sort_items('height')
        
        # 使用改进的分层装箱
        current_y = 0
        level_heights = []
        
        for item in self.items:
            placed = False
            
            # 尝试放置到现有层级
            for level_idx, level_height in enumerate(level_heights):
                # 计算该层级的剩余空间
                level_width_used = 0
                for existing_item in self.items:
                    if existing_item.y == level_height:
                        level_width_used += existing_item.width + self.spacing
                
                required_width = item.width + (self.spacing if level_width_used > 0 else 0)
                if level_width_used + required_width <= self.width_limit:
                    # 可以放置在这一层
                    item.x = level_width_used + (self.spacing if level_width_used > 0 else 0)
                    item.y = level_height
                    placed = True
                    break
            
            # 如果无法放置到现有层级，创建新层级
            if not placed:
                if item.width > self.width_limit:
                    item.rotate()
                    if item.width > self.width_limit:
                        print(f"警告：矩形 {item.index} 无法放置，尺寸过大")
                        continue
                
                item.x = 0
                item.y = current_y
                level_heights.append(current_y)
                current_y += item.height + self.spacing
        
        self.total_length = current_y - self.spacing

    def guillotine_algorithm(self):
        """断头台裁切算法（递归切割自由空间）"""
        print("使用断头台裁切算法 (Guillotine)...")
        # 自由矩形表示可放置空间: (x, y, w, h)
        free_rects: List[Tuple[float, float, float, float]] = [(0.0, 0.0, float(self.width_limit), float('inf'))]
        placed_items: List[RectangleItem] = []
        current_length = 0.0
        cuts = 0

        # 面积降序，提升填充率
        self.sort_items('area')

        for item in self.items:
            placed = False
            # 尝试两种方向
            for _ in range(2):
                for i, (fx, fy, fw, fh) in enumerate(free_rects):
                    if item.width <= fw and item.height <= fh:
                        # 放置
                        item.x = fx
                        item.y = fy
                        placed_items.append(item)
                        placed = True
                        # 更新长度
                        current_length = max(current_length, item.y + item.height)
                        # 切割：水平优先再垂直（简单启发式）
                        right_space = (fx + item.width + self.spacing, fy, max(fw - item.width - self.spacing, 0), item.height)
                        bottom_space = (fx, fy + item.height + self.spacing, fw, max(fh - item.height - self.spacing, 0))
                        # 替换当前自由块
                        free_rects.pop(i)
                        # 追加新自由块（非零尺寸）
                        if right_space[2] > 0 and right_space[3] > 0:
                            free_rects.append(right_space)
                            cuts += 1
                        if bottom_space[2] > 0 and bottom_space[3] > 0:
                            free_rects.append(bottom_space)
                            cuts += 1
                        break
                if placed:
                    break
                item.rotate()
            if not placed:
                print(f"警告：矩形 {item.index} 无法放置")

        self.total_length = current_length
        # 目标函数加权（可扩展，现在仅影响统计/导出，不更改排布结果）
        self._last_cuts = cuts

    def _score_objective(self) -> float:
        """根据当前结果计算目标成本分数。"""
        length = self.total_length
        cuts = getattr(self, '_last_cuts', 0)
        rotations = sum(1 for it in self.items if it.rotated)
        if self.objective == 'min_length':
            return length
        if self.objective == 'min_cuts':
            return cuts
        # weighted
        w = self.objective_weights
        return w.get('length', 1.0) * length + w.get('cuts', 0.0) * cuts + w.get('rotations', 0.0) * rotations
    
    def nest_rectangles(self):
        """执行矩形拼板算法"""
        if not self.items:
            print("没有可拼板的矩形")
            return False
        
        # 优化旋转
        self.optimize_rotation()
        
        # 根据选择的算法执行拼板
        if self.algorithm == PackingAlgorithm.SHELF_PACKING:
            self.shelf_packing_algorithm()
        elif self.algorithm == PackingAlgorithm.BOTTOM_LEFT_FILL:
            self.bottom_left_fill_algorithm()
        elif self.algorithm == PackingAlgorithm.BEST_FIT_DECREASING:
            self.best_fit_decreasing_algorithm()
        elif self.algorithm == PackingAlgorithm.GUILLOTINE:
            self.guillotine_algorithm()
        
        print(f"拼板完成，总长度: {self.total_length:.2f}mm")
        
        # 输出每个矩形的位置信息
        for item in self.items:
            print(f"矩形 {item.index}: 位置=({item.x:.2f}mm, {item.y:.2f}mm), "
                  f"尺寸=({item.width:.2f}mm x {item.height:.2f}mm), 旋转={item.rotated}")
        
        return True
    
    def visualize_nesting(self, output_path="nesting_visualization.png", scale=0.1):
        """可视化拼板结果"""
        try:
            import numpy as np
            import matplotlib.pyplot as plt
            import matplotlib.patches as patches
            
            # 设置中文字体
            plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
            plt.rcParams['axes.unicode_minus'] = False
            
            # 创建图形
            fig, ax = plt.subplots(1, 1, figsize=(12, 8))
            
            # 应用缩放因子
            scaled_width = self.width_limit * scale
            scaled_height = self.total_length * scale
            
            # 设置图形边界
            ax.set_xlim(0, scaled_width + 20)
            ax.set_ylim(0, scaled_height + 20)
            
            # 绘制每个矩形
            colors = plt.cm.Set3(np.linspace(0, 1, len(self.items)))
            
            for i, item in enumerate(self.items):
                if item.x == 0 and item.y == 0 and not hasattr(item, '_placed'):
                    continue  # 跳过未放置的矩形
                    
                # 应用缩放
                x = item.x * scale
                y = item.y * scale
                width = item.width * scale
                height = item.height * scale
                
                # 创建矩形
                rect = patches.Rectangle((x, y), width, height, 
                                       linewidth=1, edgecolor='black', 
                                       facecolor=colors[i], alpha=0.7)
                ax.add_patch(rect)
                
                # 添加标签
                ax.text(x + width/2, y + height/2, str(item.index), 
                       ha='center', va='center', fontsize=8, fontweight='bold')
                
                # 如果旋转了，添加旋转标记
                if item.rotated:
                    ax.plot([x, x + width], [y, y + height], 'r-', linewidth=2)
            
            # 添加网格
            ax.grid(True, alpha=0.3)
            
            # 设置标题和标签
            ax.set_title(f'拼板结果 - 算法: {self.algorithm.value}\n'
                        f'尺寸: {self.width_limit}mm x {self.total_length:.2f}mm\n'
                        f'利用率: {(sum(item.area for item in self.items) / (self.width_limit * self.total_length) * 100):.1f}%')
            ax.set_xlabel('宽度 (mm)')
            ax.set_ylabel('长度 (mm)')
            
            # 保存图像
            plt.tight_layout()
            plt.savefig(output_path, dpi=300, bbox_inches='tight')
            plt.close()
            
            print(f"拼板可视化结果已保存到: {output_path}")
            return output_path
            
        except ImportError:
            print("可视化功能需要安装matplotlib库：pip install matplotlib")
            return None

    def export_svg(self, output_path: str, scale: float = 1.0) -> bool:
        """导出 SVG 矢量可视化（不依赖第三方库）。"""
        try:
            width = max(self.width_limit * scale, 1)
            height = max(self.total_length * scale, 1)
            # 基础 SVG 头
            svg_parts = []
            svg_parts.append(f'<svg xmlns="http://www.w3.org/2000/svg" width="{width:.2f}" height="{height:.2f}" viewBox="0 0 {width:.2f} {height:.2f}">')
            # 背景
            svg_parts.append(f'<rect x="0" y="0" width="{width:.2f}" height="{height:.2f}" fill="#ffffff" stroke="#ddd"/>')
            # 矩形
            for i, item in enumerate(self.items):
                if self.total_length <= 0:
                    continue
                x = item.x * scale
                y = item.y * scale
                w = max(item.width * scale, 0.1)
                h = max(item.height * scale, 0.1)
                # 简单配色：HSL 按索引变化
                hue = int((i * 137) % 360)
                fill = f'hsl({hue},70%,70%)'
                svg_parts.append(f'<rect x="{x:.2f}" y="{y:.2f}" width="{w:.2f}" height="{h:.2f}" fill="{fill}" stroke="#333" stroke-width="0.8"/>')
                # 标签
                cx = x + w/2
                cy = y + h/2
                svg_parts.append(f'<text x="{cx:.2f}" y="{cy:.2f}" text-anchor="middle" dominant-baseline="central" font-size="10" fill="#000">{item.index}</text>')
            svg_parts.append('</svg>')
            with open(output_path, 'w', encoding='utf-8') as f:
                f.write('\n'.join(svg_parts))
            print(f"SVG 已导出到: {output_path}")
            return True
        except Exception as e:
            print(f"导出SVG失败: {str(e)}")
            return False

    def export_html_report(self, output_path: str, svg_path: Optional[str] = None, inline_svg: bool = False, scale: float = 1.0) -> bool:
        """导出 HTML 报告，包含统计信息和 SVG 预览。"""
        try:
            stats = self.calculate_statistics()
            svg_content = ''
            if inline_svg:
                # 内联 SVG
                import io
                buf = io.StringIO()
                width = max(self.width_limit * scale, 1)
                height = max(self.total_length * scale, 1)
                buf.write(f'<svg xmlns="http://www.w3.org/2000/svg" width="{width:.2f}" height="{height:.2f}" viewBox="0 0 {width:.2f} {height:.2f}">')
                buf.write(f'<rect x="0" y="0" width="{width:.2f}" height="{height:.2f}" fill="#ffffff" stroke="#ddd"/>')
                for i, item in enumerate(self.items):
                    x = item.x * scale
                    y = item.y * scale
                    w = max(item.width * scale, 0.1)
                    h = max(item.height * scale, 0.1)
                    hue = int((i * 137) % 360)
                    fill = f'hsl({hue},70%,70%)'
                    buf.write(f'<rect x="{x:.2f}" y="{y:.2f}" width="{w:.2f}" height="{h:.2f}" fill="{fill}" stroke="#333" stroke-width="0.8"/>')
                    cx = x + w/2
                    cy = y + h/2
                    buf.write(f'<text x="{cx:.2f}" y="{cy:.2f}" text-anchor="middle" dominant-baseline="central" font-size="10" fill="#000">{item.index}</text>')
                buf.write('</svg>')
                svg_content = buf.getvalue()
            html = []
            html.append('<!DOCTYPE html>')
            html.append('<html lang="zh-CN"><head><meta charset="utf-8"/>')
            html.append('<meta name="viewport" content="width=device-width, initial-scale=1"/>')
            html.append('<title>拼板报告</title>')
            html.append('<style>body{font-family:-apple-system,Segoe UI,Roboto,Arial,Helvetica,sans-serif;padding:16px;} .kv{display:grid;grid-template-columns:160px 1fr;gap:8px 16px;margin-bottom:16px;} .preview{border:1px solid #eee;padding:8px;overflow:auto;} code{background:#f6f8fa;padding:2px 4px;border-radius:4px;}</style>')
            html.append('</head><body>')
            html.append(f'<h2>拼板报告 - {self.algorithm.value}</h2>')
            html.append('<div class="kv">')
            for k, v in stats.items():
                html.append(f'<div><strong>{k}</strong></div><div>{v}</div>')
            html.append('</div>')
            # 预览
            html.append('<div class="preview">')
            if inline_svg:
                html.append(svg_content)
            elif svg_path:
                html.append(f'<object type="image/svg+xml" data="{os.path.basename(svg_path)}" style="width:100%;height:auto;"></object>')
            html.append('</div>')
            html.append('</body></html>')
            with open(output_path, 'w', encoding='utf-8') as f:
                f.write('\n'.join(html))
            print(f"HTML 报告已导出到: {output_path}")
            return True
        except Exception as e:
            print(f"导出HTML失败: {str(e)}")
            return False
        except Exception as e:
            print(f"创建可视化图像时出错: {str(e)}")
            return None
    
    def export_results(self, output_path):
        """导出拼板结果到JSON文件"""
        results = {
            "algorithm": self.algorithm.value,
            "width_limit_mm": self.width_limit,
            "spacing_mm": self.spacing,
            "dpi": self.dpi,
            "total_length_mm": self.total_length,
            "total_width_mm": self.width_limit,
            "utilization_rate": sum(item.area for item in self.items) / (self.width_limit * self.total_length) * 100 if self.total_length > 0 else 0,
            "rectangles": []
        }
        
        for item in self.items:
            rect_data = {
                "index": item.index,
                "source_page": item.source_page,
                "width_mm": round(item.width, 2),
                "height_mm": round(item.height, 2),
                "x_mm": round(item.x, 2),
                "y_mm": round(item.y, 2),
                "rotated": item.rotated,
                "area_mm2": round(item.area, 2)
            }
            results["rectangles"].append(rect_data)
        
        try:
            with open(output_path, 'w', encoding='utf-8') as f:
                json.dump(results, f, ensure_ascii=False, indent=2)
            print(f"拼板结果已导出到: {output_path}")
            return True
        except Exception as e:
            print(f"导出结果时出错: {str(e)}")
            return False
    
    def calculate_statistics(self):
        """计算拼板统计信息"""
        if not self.items:
            return {}
        
        total_area = sum(item.area for item in self.items)
        used_area = self.width_limit * self.total_length
        utilization = (total_area / used_area * 100) if used_area > 0 else 0
        
        rotated_count = sum(1 for item in self.items if item.rotated)
        
        stats = {
            "total_rectangles": len(self.items),
            "total_area_mm2": round(total_area, 2),
            "used_area_mm2": round(used_area, 2),
            "utilization_rate_percent": round(utilization, 2),
            "rotated_rectangles": rotated_count,
            "rotation_rate_percent": round(rotated_count / len(self.items) * 100, 2),
            "average_rectangle_area_mm2": round(total_area / len(self.items), 2)
        }
        
        return stats

def main():
    """主函数，提供命令行接口"""
    parser = argparse.ArgumentParser(description='PDF矩形拼板工具 - 优化版本')
    
    # 输入选项
    parser.add_argument('--json-path', type=str, help='单个JSON结果文件路径')
    parser.add_argument('--json-dir', type=str, help='JSON目录，批量处理该目录下所有 *_rectangle_results.json')
    
    # 输出选项
    parser.add_argument('--export-json', type=str, help='拼板结果JSON导出路径（单文件或目录模式下的输出文件/目录）')
    parser.add_argument('--visualize', type=str, help='可视化图像输出路径（PNG或SVG）')
    parser.add_argument('--export-svg', action='store_true', help='导出SVG而非PNG')
    parser.add_argument('--export-html', type=str, help='导出HTML报告到指定路径（目录模式下自动为每个任务命名）')
    
    # 拼板参数
    parser.add_argument('--width-limit', type=float, default=1000, help='拼板宽度限制(mm)，默认值为1000mm')
    parser.add_argument('--spacing', type=float, default=10, help='矩形间距(mm)，默认值为10mm')
    parser.add_argument('--dpi', type=int, default=300, help='PDF的DPI，默认值为300')
    parser.add_argument('--algorithm', type=str, choices=['shelf_packing', 'bottom_left_fill', 'best_fit_decreasing', 'guillotine'],
                        default='shelf_packing', help='拼板算法，默认值为shelf_packing')
    parser.add_argument('--scale', type=float, default=0.1, help='可视化缩放因子，默认值为0.1')
    parser.add_argument('--objective', type=str, choices=['min_length', 'min_cuts', 'weighted'], default='min_length', help='目标函数')
    parser.add_argument('--objective-weights', type=str, help='加权目标的权重JSON字符串，如 {"length":1,"cuts":0.2,"rotations":0.1}')
    
    args = parser.parse_args()
    if not args.json_path and not args.json_dir:
        parser.error('必须提供 --json-path 或 --json-dir 之一')
    
    # 转换算法类型
    algorithm_map = {
        'shelf_packing': PackingAlgorithm.SHELF_PACKING,
        'bottom_left_fill': PackingAlgorithm.BOTTOM_LEFT_FILL,
        'best_fit_decreasing': PackingAlgorithm.BEST_FIT_DECREASING,
        'guillotine': PackingAlgorithm.GUILLOTINE
    }
    algorithm = algorithm_map[args.algorithm]
    
    # 创建拼板实例
    # 解析 objective weights
    weights = None
    if args.objective_weights:
        try:
            weights = json.loads(args.objective_weights)
        except Exception:
            print('警告：objective-weights 解析失败，使用默认权重')

    def run_single(json_path: str, export_json_path: Optional[str], visualize_path: Optional[str], export_svg: bool, export_html_path: Optional[str]):
        nesting = RectangleNestingOptimized(
        width_limit=args.width_limit,
        spacing=args.spacing,
        dpi=args.dpi,
        algorithm=algorithm,
        objective=args.objective,
        objective_weights=weights
        )

        success = nesting.load_from_json(json_path)
        if not success:
            print(f"加载失败: {json_path}")
            return 1
        if not nesting.nest_rectangles():
            print(f"拼板失败: {json_path}")
            return 1

        stats = nesting.calculate_statistics()
        print("\n=== 拼板统计信息 ===")
        for key, value in stats.items():
            print(f"{key}: {value}")

        # 导出 JSON
        if export_json_path:
            nesting.export_results(export_json_path)

        # 可视化（PNG 或 SVG）
        if visualize_path:
            if export_svg or (visualize_path.lower().endswith('.svg')):
                nesting.export_svg(visualize_path, scale=args.scale)
            else:
                nesting.visualize_nesting(visualize_path, args.scale)

        # HTML 报告
        if export_html_path:
            # 如果指定了SVG导出，优先使用该文件在报告中嵌入
            svg_used = None
            if visualize_path and (export_svg or visualize_path.lower().endswith('.svg')):
                svg_used = visualize_path
            nesting.export_html_report(export_html_path, svg_path=svg_used, inline_svg=(svg_used is None), scale=args.scale)

        return 0

    # 单文件模式
    if args.json_path:
        return run_single(
            args.json_path,
            args.export_json,
            args.visualize,
            args.export_svg,
            args.export_html
        )

    # 目录模式：批处理
    assert args.json_dir
    if not os.path.isdir(args.json_dir):
        print(f"JSON目录不存在: {args.json_dir}")
        return 1
    json_files = [
        os.path.join(args.json_dir, f) for f in os.listdir(args.json_dir)
        if f.endswith('_rectangle_results.json')
    ]
    if not json_files:
        print(f"目录下未找到 *_rectangle_results.json: {args.json_dir}")
        return 1

    # 输出目录准备
    export_json_dir = None
    visualize_dir = None
    export_html_dir = None
    if args.export_json:
        export_json_dir = args.export_json if os.path.isdir(args.export_json) or args.export_json.endswith('/') else None
        if export_json_dir is None:
            # 创建目录
            export_json_dir = args.export_json
        os.makedirs(export_json_dir, exist_ok=True)
    if args.visualize:
        visualize_dir = args.visualize if os.path.isdir(args.visualize) or args.visualize.endswith('/') else None
        if visualize_dir is None:
            visualize_dir = args.visualize
        os.makedirs(visualize_dir, exist_ok=True)
    if args.export_html:
        export_html_dir = args.export_html if os.path.isdir(args.export_html) or args.export_html.endswith('/') else None
        if export_html_dir is None:
            export_html_dir = args.export_html
        os.makedirs(export_html_dir, exist_ok=True)

    exit_codes = []
    for json_path in sorted(json_files):
        base = os.path.splitext(os.path.basename(json_path))[0]
        out_json = os.path.join(export_json_dir, f"{base}_nesting.json") if export_json_dir else None
        if visualize_dir:
            ext = 'svg' if args.export_svg else 'png'
            out_vis = os.path.join(visualize_dir, f"{base}_nest.{ext}")
        else:
            out_vis = None
        out_html = os.path.join(export_html_dir, f"{base}_report.html") if export_html_dir else None
        code = run_single(json_path, out_json, out_vis, args.export_svg, out_html)
        exit_codes.append(code)

    return 0 if all(c == 0 for c in exit_codes) else 1

if __name__ == '__main__':
    exit_code = main()
    import sys
    sys.exit(exit_code)
